package com.iraytek.rtspdemo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CameraStateCallback extends CameraDevice.StateCallback {
    private static final String TAG = "CameraStateCallback";
    public static boolean restartFlag = false;
    Surface vSurface, iSurface;
    ImageReader imageReader, takePhotoReader;
    CameraCaptureSession captureSession;
    CameraDevice camera;
    private final int format;
    private int cameraIndex;
    private float zoom;
    private Size size;
    private Handler handler;
    private ExecutorService executor;
    private CaptureRequest.Builder requestBuilder;
    private final ImageReader.OnImageAvailableListener onImageAvailableListener;
    private final CameraUtil.SurfaceTextureHolder surfaceTextureHolder;

    public CameraStateCallback(int format, ImageReader.OnImageAvailableListener onImageAvailableListener, @Nullable CameraUtil.SurfaceTextureHolder surfaceTextureHolder) {
        this.format = format;
        this.onImageAvailableListener = onImageAvailableListener;
        this.surfaceTextureHolder = surfaceTextureHolder;
    }

    public int getFormat() {
        return format;
    }

    public ImageReader.OnImageAvailableListener getOnImageAvailableListener() {
        return onImageAvailableListener;
    }

    public Size getSize() {
        return size;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public void setHandler(Handler handler) {
        this.handler = handler;
    }

    public int getCameraIndex() {
        return cameraIndex;
    }

    public void setCameraIndex(int cameraIndex) {
        this.cameraIndex = cameraIndex;
    }

    @Override
    public void onOpened(@NonNull CameraDevice camera) {
           /* //设置帧率
            fpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRanges[0]);*/
        this.camera = camera;
        Log.d(TAG, "onOpened: size=" + size);
        imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, 2);
        imageReader.setOnImageAvailableListener(onImageAvailableListener, handler);
        startPreview();
    }

    private void startPreview() {
        try {
            requestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.setTag(camera.getId());

            List<Surface> outputSurfaces = new ArrayList<>(2);
            if (surfaceTextureHolder != null) {
                SurfaceTexture surfaceTexture = surfaceTextureHolder.getSurfaceTexture();
                if (surfaceTexture != null) {
                    surfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
                    vSurface = new Surface(surfaceTexture);
                    outputSurfaces.add(vSurface);
                }
            }

            iSurface = imageReader.getSurface();
            outputSurfaces.add(iSurface);

            for (Surface surface : outputSurfaces) {
                requestBuilder.addTarget(surface);
            }

            if (format == ImageFormat.YUV_420_888) {//添加拍照用的surface
                Size size = CameraUtil.checkSize(camera.getId(), ImageFormat.JPEG, 0);
                takePhotoReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), ImageFormat.JPEG, 2);
                outputSurfaces.add(takePhotoReader.getSurface());
            }

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                executor = Executors.newSingleThreadExecutor();
                List<OutputConfiguration> configurations = new ArrayList<>(2);
                for (Surface surface : outputSurfaces) {
                    OutputConfiguration configuration = new OutputConfiguration(surface);
                    configurations.add(configuration);
                }
                SessionConfiguration sessionConfiguration = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR, configurations, executor, new SessionStateCallback());
                camera.createCaptureSession(sessionConfiguration);
            } else {
                camera.createCaptureSession(outputSurfaces, new SessionStateCallback(), null);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public Bitmap takePhoto() {
        if (camera == null || takePhotoReader == null) {
            return null;
        }
        try {
            ArrayBlockingQueue<Bitmap> blockingQueue = new ArrayBlockingQueue<>(1);
            takePhotoReader.setOnImageAvailableListener(reader -> {
                Image image = reader.acquireLatestImage();
                Image.Plane plane = image.getPlanes()[0];
                ByteBuffer buffer = plane.getBuffer();
                byte[] b = new byte[buffer.capacity()];
                buffer.get(b);
                Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
                blockingQueue.add(bitmap);
                image.close();
            }, handler);
            CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
            builder.addTarget(takePhotoReader.getSurface());
            captureSession.capture(builder.build(), new CaptureCallback(camera.getId()), handler);
            return blockingQueue.poll(3000, TimeUnit.MILLISECONDS);
        } catch (CameraAccessException | InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {
        camera.close();
        close();
        if (restartFlag) {
            restart();
        }
    }

    @Override
    public void onError(@NonNull CameraDevice camera, int error) {//see CameraDevice.StateCallback for error code
        String errMsg = "onError: failed to open camera, cameraId=" + camera.getId() + " error code=" + error;
        Log.e(TAG, errMsg);
        camera.close();
        close();
        if (restartFlag) {
            restart();
        }
    }

    @SuppressLint("MissingPermission")
    private void restart() {
        HandlerThread thread = new HandlerThread(TAG + cameraIndex);
        thread.start();
        handler = new Handler(thread.getLooper());
        CameraManager cameraManager = (CameraManager) RtspApplication.context.getSystemService(Context.CAMERA_SERVICE);
        try {
            cameraManager.openCamera(camera.getId(), this, handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public void close() {
        if (captureSession != null) {
            captureSession.close();
        }
        if (camera != null) {
            camera.close();
        }
        if (imageReader != null) {
            imageReader.close();
        }
        if (handler != null) {
            handler.getLooper().quit();
        }
        if (executor != null) {
            executor.shutdown();
        }
    }

    public void setZoom(float zoom) {
        if (captureSession == null) {
            this.zoom = zoom;
            return;
        }
        try {
            Rect newRect = CameraUtil.getScaledRect(camera.getId(), zoom);
            requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, newRect);
            captureSession.setRepeatingRequest(requestBuilder.build(), new CaptureCallback(camera.getId()), handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
            CameraUtil.openCamera(cameraIndex, format, onImageAvailableListener, surfaceTextureHolder, size.getWidth());
        }
    }

    class CaptureCallback extends CameraCaptureSession.CaptureCallback {
        private final String cameraId;

        public CaptureCallback(String cameraId) {
            this.cameraId = cameraId;
        }

        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
        }

        @Override
        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
            Log.w(TAG, "onCaptureFailed: cameraId=" + cameraId + " fail reason=" + failure.getReason());
            if (restartFlag) {
                close();
                restart();
            }
        }


        @Override
        public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) {
            Log.w(TAG, "onCaptureSequenceAborted: cameraId=" + cameraId);
        }

        @Override
        public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
            Log.w(TAG, "onCaptureBufferLost: " + target + " cameraId=" + cameraId + "  frameNo=" + frameNumber);
        }
    }

    class SessionStateCallback extends CameraCaptureSession.StateCallback {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            captureSession = session;
            try {
                if (zoom > 0) {
                    requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, CameraUtil.getScaledRect(camera.getId(), zoom));
                }
                requestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                captureSession.setRepeatingRequest(requestBuilder.build(), new CaptureCallback(session.getDevice().getId()), handler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            Log.e(TAG, "onConfigureFailed: cameraId=" + session.getDevice().getId());
            session.close();
        }
    }
}
